package cn.cloud.all.security.authentication.www;

import cn.cloud.all.security.authentication.AuthenticationConverter;
import cn.cloud.all.security.authentication.AuthenticationDetailsSource;
import cn.cloud.all.security.authentication.UsernamePasswordAuthenticationToken;
import cn.cloud.all.security.authentication.WebAuthenticationDetailsSource;
import cn.cloud.all.security.authentication.exception.BadCredentialsException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * Converts from a HttpServletRequest to
 * {@link UsernamePasswordAuthenticationToken} that can be authenticated. Null
 * authentication possible if there was no Authorization header with Basic
 * authentication scheme.
 */
public class BasicAuthenticationConverter implements AuthenticationConverter {

    public static final String AUTHENTICATION_SCHEME_BASIC = "Basic";

    private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;

    private Charset credentialsCharset = StandardCharsets.UTF_8;

    public BasicAuthenticationConverter() {
        this(new WebAuthenticationDetailsSource());
    }

    public BasicAuthenticationConverter(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
        this.authenticationDetailsSource = authenticationDetailsSource;
    }

    public Charset getCredentialsCharset() {
        return this.credentialsCharset;
    }

    public void setCredentialsCharset(Charset credentialsCharset) {
        this.credentialsCharset = credentialsCharset;
    }

    public AuthenticationDetailsSource<HttpServletRequest, ?> getAuthenticationDetailsSource() {
        return this.authenticationDetailsSource;
    }

    public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
        Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
        this.authenticationDetailsSource = authenticationDetailsSource;
    }

    @Override
    public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header == null) {
            return null;
        }

        header = header.trim();
        if (!StringUtils.startsWithIgnoreCase(header, "Basic")) {
            return null;
        }

        byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, this.credentialsCharset);

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1));
        result.setDetails(this.authenticationDetailsSource.buildDetails(request));
        return result;
    }

    protected Charset getCredentialsCharset(HttpServletRequest request) {
		return this.credentialsCharset;
    }
}
